use proc_macro::TokenStream;

use syn::{Data, DeriveInput, Ident, parse_macro_input, Type};

pub fn optional(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let struct_vis_name = input.vis;

    let ident = input.ident;

    let Data::Struct(data_struct) = input.data else {
        return syn::Error::new(ident.span(), "optional can only be applied to structs")
            .into_compile_error()
            .into();
    };

    let optional_struct_name = &format!("{}Optional", ident);
    let optional_struct_ident = Ident::new(optional_struct_name, ident.span());

    let fields: Vec<_> = data_struct
        .fields
        .iter()
        .map(|field| {
            let field_vis_name = &field.vis;

            let mut ident = field.ident.clone();

            let ty = &field.ty;
            let mut is_skip = false;

            let attr = field
                .attrs
                .iter()
                .find(|attr| attr.path().is_ident("optional"));

            if let Some(attr) = attr {
                let _ = attr.parse_nested_meta(|meta| {
                    if meta.path.is_ident("skip") {
                        is_skip = true;
                    } else if meta.path.is_ident("rename") {
                        let renamed_ident = meta.value()?.parse::<syn::Ident>()?;
                        ident = Some(renamed_ident)
                    }
                    Ok(())
                });
            }

            if is_skip || is_option(ty) {
                return quote::quote!(#ident:#ty);
            };
            quote::quote!(#field_vis_name #ident:Option<#ty>)
        })
        .collect();

    quote::quote!(
        #struct_vis_name struct #optional_struct_ident {
            #(#fields,)*
        }
    )
        .into()
}


fn is_option(ty: &Type) -> bool {
    let Type::Path(path) = ty else {
        return false;
    };
    let path = &path.path;
    path.segments
        .last()
        .map(|segment| segment.ident == "Option")
        .unwrap_or(false)
}
